﻿/**
* jquery.contextmenu.js
* jQuery Plugin for Context Menus
* http://www.JavascriptToolbox.com/lib/contextmenu/
*
* Copyright (c) 2008 Matt Kruse (javascripttoolbox.com)
* Dual licensed under the MIT and GPL licenses.
*
* @version 1.0
* @history 1.0 2008-10-20 Initial Release
* @todo slideUp doesn't work in IE - because of iframe?
* @todo Hide all other menus when contextmenu is shown?
* @todo More themes
* @todo Nested context menus
*/
;(function($) {
    $.contextMenu = {
        shadow: true,
        shadowOffset: 0,
        shadowOffsetX: 5,
        shadowOffsetY: 5,
        shadowWidthAdjust: -3,
        shadowHeightAdjust: -3,
        shadowOpacity: .2,
        shadowClass: 'context-menu-shadow',
        shadowColor: 'black',
        align: 'left', //left | right (determines how the context menu is aligned to its target)

        modal: false, //if modal, the context menu stays active even when user clicks  

        offsetX: 0,
        offsetY: 0,
        appendTo: 'body',
        direction: 'down',
        constrainToScreen: true,

        showTransition: 'show',
        hideTransition: 'hide',
        showSpeed: '',
        hideSpeed: '',
        showCallback: null,
        hideCallback: null,

        className: 'context-menu',
        itemClassName: 'context-menu-item',
        itemHoverClassName: 'context-menu-item-hover',
        disabledItemClassName: 'context-menu-item-disabled',
        disabledItemHoverClassName: 'context-menu-item-disabled-hover',
        separatorClassName: 'context-menu-separator',
        innerDivClassName: 'context-menu-item-inner',
        themePrefix: 'context-menu-theme-',
        theme: 'default',

        separator: 'context-menu-separator', // A specific key to identify a separator
        target: null, // The target of the context click, to be populated when triggered
        menu: null, // The jQuery object containing the HTML object that is the menu itself
        shadowObj: null, // Shadow object
        bgiframe: null, // The iframe object for IE6
        shown: false, // Currently being shown?
        useIframe: /* @cc_on@ */ /*@if (@_win32)true, @else @ */false, /*@end@*/ // This is a better check than looking at userAgent!

        // Create the menu instance
        create: function(menu, opts) {
            var cmenu = $.extend({}, this, opts); // Clone all default properties to created object

            // If a selector has been passed in, then use that as the menu
            if (typeof menu == "string") {
                cmenu.menu = $(menu);
            }
            // If a function has been passed in, call it each time the menu is shown to create the menu
            else if (typeof menu == "function") {
                cmenu.menuFunction = menu;
            }
            // Otherwise parse the Array passed in
            else {
                cmenu.menu = cmenu.createMenu(menu, cmenu);
            }
            if (cmenu.menu) {
                cmenu.menu.css({ display: 'none' });
                $(cmenu.appendTo).append(cmenu.menu);
            }

            // Create the shadow object if shadow is enabled
            if (cmenu.shadow) {
                cmenu.createShadow(cmenu); // Extracted to method for extensibility
                if (cmenu.shadowOffset) { cmenu.shadowOffsetX = cmenu.shadowOffsetY = cmenu.shadowOffset; }
            }
            //$('body').bind('contextmenu', function() { cmenu.hide(); }); // If right-clicked somewhere else in the document, hide this menu
            if (!cmenu.modal)
                $('body').bind('click', function() { cmenu.hide(); }); // If right-clicked somewhere else in the document, hide this menu
             
            return cmenu;
        },

        // Create an iframe object to go behind the menu
        createIframe: function() {
            return $('<iframe frameborder="0" tabindex="-1" src="javascript:false" style="display:block;position:absolute;z-index:-1;filter:Alpha(Opacity=0);"/>');
        },

        // Accept an Array representing a menu structure and turn it into HTML
        createMenu: function(menu, cmenu) {
            var className = cmenu.className;
            $.each(cmenu.theme.split(","), function(i, n) { className += ' ' + cmenu.themePrefix + n });
            var $t = $('<table cellspacing=0 cellpadding=0></table>').click(function() { if (!modal) cmenu.hide(); return false; }); // We wrap a table around it so width can be flexible
            var $tr = $('<tr></tr>');
            var $td = $('<td></td>');
            var $div = $('<div class="' + className + '"></div>');

            // Each menu item is specified as either:
            //     title:function
            // or  title: { property:value ... }
            for (var i = 0; i < menu.length; i++) {
                var m = menu[i];
                if (m == $.contextMenu.separator) {
                    $div.append(cmenu.createSeparator());
                }
                else {
                    for (var opt in menu[i]) {
                        $div.append(cmenu.createMenuItem(opt, menu[i][opt])); // Extracted to method for extensibility
                    }
                }
            }
            if (cmenu.useIframe) {
                $td.append(cmenu.createIframe());
            }
            $t.append($tr.append($td.append($div)))
            return $t;
        },

        // Create an individual menu item
        createMenuItem: function(label, obj) {
            var cmenu = this;
            if (typeof obj == "function") { obj = { onclick: obj }; } // If passed a simple function, turn it into a property of an object
            // Default properties, extended in case properties are passed
            var o = $.extend({
                onclick: function() { },
                className: '',
                hoverClassName: cmenu.itemHoverClassName,
                icon: '',
                disabled: false,
                title: '',
                hoverItem: cmenu.hoverItem,
                hoverItemOut: cmenu.hoverItemOut
            }, obj);
            // If an icon is specified, hard-code the background-image style. Themes that don't show images should take this into account in their CSS
            var iconStyle = (o.icon) ? 'background-image:url(' + o.icon + ');' : '';
            var $div = $('<div class="' + cmenu.itemClassName + ' ' + o.className + ((o.disabled) ? ' ' + cmenu.disabledItemClassName : '') + '" title="' + o.title + '"></div>')
            // If the item is disabled, don't do anything when it is clicked
              .click(function(e) { if (cmenu.isItemDisabled(this)) { return false; } else { return o.onclick.call(cmenu.target, this, cmenu, e) } })
            // Change the class of the item when hovered over
              .hover(function() { o.hoverItem.call(this, (cmenu.isItemDisabled(this)) ? cmenu.disabledItemHoverClassName : o.hoverClassName); }
                  , function() { o.hoverItemOut.call(this, (cmenu.isItemDisabled(this)) ? cmenu.disabledItemHoverClassName : o.hoverClassName); }
              );
            var $idiv = $('<div class="' + cmenu.innerDivClassName + '" style="' + iconStyle + '">' + label + '</div>');
            $div.append($idiv);
            return $div;
        },

        // Create a separator row
        createSeparator: function() {
            return $('<div class="' + this.separatorClassName + '"></div>');
        },

        // Determine if an individual item is currently disabled. This is called each time the item is hovered or clicked because the disabled status may change at any time
        isItemDisabled: function(item) { return $(item).is('.' + this.disabledItemClassName); },

        // Functions to fire on hover. Extracted to methods for extensibility
        hoverItem: function(c) { $(this).addClass(c); },
        hoverItemOut: function(c) { $(this).removeClass(c); },

        // Create the shadow object
        createShadow: function(cmenu) {
            cmenu.shadowObj = $('<div class="' + cmenu.shadowClass + '"></div>').css({ display: 'none', position: "absolute", zIndex: 9998, opacity: cmenu.shadowOpacity, backgroundColor: cmenu.shadowColor });
            $(cmenu.appendTo).append(cmenu.shadowObj);
        },

        // Display the shadow object, given the position of the menu itself
        showShadow: function(x, y, e) {
            var cmenu = this;
            if (cmenu.shadow) {
                cmenu.shadowObj.css({
                    width: (cmenu.menu.width() + cmenu.shadowWidthAdjust) + "px",
                    height: (cmenu.menu.height() + cmenu.shadowHeightAdjust) + "px",
                    top: (y + cmenu.shadowOffsetY) + "px",
                    left: (x + cmenu.shadowOffsetX) + "px"
                }).addClass(cmenu.shadowClass)[cmenu.showTransition](cmenu.showSpeed);
            }
        },

        // A hook to call before the menu is shown, in case special processing needs to be done.
        // Return false to cancel the default show operation
        beforeShow: function() { return true; },

        // Show the context menu
        show: function(t, e) {
            var cmenu = this, x = null, y = null;

            if (cmenu.direction == "up") { y = $(t).offset().top; }
            else { y = $(t).offset().top + $(t).height(); }
            if (cmenu.align == "left") { x = $(t).offset().left; }
            else { x = $(t).offset().left - (cmenu.menu.width() - $(t).width()); }

            //!!CHANGED x = e.pageX, y = e.pageY;
            cmenu.target = t; // Preserve the object that triggered this context menu so menu item click methods can see it
            if (cmenu.beforeShow() !== false) {
                // If the menu content is a function, call it to populate the menu each time it is displayed
                if (cmenu.menuFunction) {
                    if (cmenu.menu) { $(cmenu.menu).remove(); }
                    cmenu.menu = cmenu.createMenu(cmenu.menuFunction(cmenu, t), cmenu);
                    cmenu.menu.css({ display: 'none' });
                    $(cmenu.appendTo).append(cmenu.menu);
                }
                var $c = cmenu.menu;
                x += cmenu.offsetX; y += cmenu.offsetY;
                var pos = cmenu.getPosition(x, y, cmenu, e); // Extracted to method for extensibility
                cmenu.showShadow(pos.x, pos.y, e);
                // Resize the iframe if needed
                if (cmenu.useIframe) {
                    $c.find('iframe').css({ width: $c.width() + cmenu.shadowOffsetX + cmenu.shadowWidthAdjust, height: $c.height() + cmenu.shadowOffsetY + cmenu.shadowHeightAdjust });
                }
                $c.css({ top: pos.y + "px", left: pos.x + "px", position: "absolute", zIndex: 9999 })[cmenu.showTransition](cmenu.showSpeed, ((cmenu.showCallback) ? function() { cmenu.showCallback.call(cmenu); } : null));
                cmenu.shown = true;
                $(document).one('click', null, function() { if (!cmenu.modal) cmenu.hide() }); // Handle a single click to the document to hide the menu
            }
        },


        // Find the position where the menu should appear, given an x,y of the click event
        getPosition: function(clickX, clickY, cmenu, e) {
            var x = clickX + cmenu.offsetX;
            var y = clickY + cmenu.offsetY
            var h = $(cmenu.menu).height();
            var w = $(cmenu.menu).width();
            var dir = cmenu.direction;
            if (cmenu.constrainToScreen) {
                var $w = $(window);
                var wh = $w.height();
                var ww = $w.width();
                if (dir == "down" && (y + h - $w.scrollTop() > wh)) { dir = "up"; }
                var maxRight = x + w - $w.scrollLeft();
                if (maxRight > ww) { x -= (maxRight - ww); }
            }
            if (dir == "up") { y -= h; }
            return { 'x': x, 'y': y };
        },

        // Hide the menu, of course
        hide: function() {
            var cmenu = this;
            if (cmenu.shown) {
                if (cmenu.iframe) { $(cmenu.iframe).hide(); }
                if (cmenu.menu) { cmenu.menu[cmenu.hideTransition](cmenu.hideSpeed, ((cmenu.hideCallback) ? function() { cmenu.hideCallback.call(cmenu); } : null)); }
                if (cmenu.shadow) { cmenu.shadowObj[cmenu.hideTransition](cmenu.hideSpeed); }
            }
            cmenu.shown = false;
        }
    };

    // This actually adds the .contextMenu() function to the jQuery namespace
    $.fn.contextMenu = function(menu, options) {
        if (options == "hide") {
            var cmenu = $.contextMenu.create(menu);
            $.contextMenu.hide();
            return;
        }

        var cmenu = $.contextMenu.create(menu, options);
        return this.each(function() {
            //CHANGED!! $(this).bind('contextmenu', function(e) { cmenu.show(this, e); return false; });
            $(this).bind('click', function(e) { cmenu.show(this, e); return false; });
        });
    };
})(jQuery);
